import * as THREE from "three"
import { SweepHoverCommand } from "../command/sweep.command"
import RenderLayers, { RenderLayer } from "../utils/RenderLayers"
import sweepAlignmentEnum from "../enum/sweepAlignment.enum"
import PuckMesh from "../mesh/PuckMesh"
import PuckCollider from "../mesh/PuckCollider"
import renderOrderEnum from "../enum/renderOrder.enum"
import { ClickEvent, HoverEvent, UnhoverEvent } from "../event/GestureEvent"
import keyConst from "../constant/key.const"
import { PuckClickedMessage, PuckHoverMessage } from "../message/puck.message"
import SettingsData from "../data/SettingsData"
import SweepViewData from "../data/SweepViewData"
import Sweep from "../object/Sweep"
import { sweepStatus } from "../module/SweepPucksModule"
import EngineContext from "../EngineContext"
import { Subscription } from "../utils/Subscription"
import InputIniModule, { InputSubscription } from "../module/InputIniModule"
import WebGLScene from "../three/WebGLScene"

export default class PuckRenderer {
  scene: WebGLScene
  input: InputIniModule
  settingsData: SettingsData
  sweepViewData: SweepViewData
  puckImagery: typeof sweepStatus
  onFloor: boolean
  checkRenderModes: any
  idleColor: THREE.Color
  selectionColor: THREE.Color
  idleOpacity: number
  editingOpacity: number
  selectionDuration: number
  puckSize: number
  layer: RenderLayer
  sweepToMesh: Record<string, PuckMesh>
  colliderToSweep: Record<string, string>
  editingEnabled: boolean
  dummyMat: THREE.MeshBasicMaterial
  puckOffset: THREE.Vector3
  bindings: (Subscription | InputSubscription)[]
  wasDrawing: boolean
  renderPuckHighlight: (e: any, t: any) => void
  updateLookAt: (e: any) => void
  puckGeometry: THREE.PlaneGeometry
  sphereColliderGeometry: THREE.SphereGeometry
  matParamsEnabled: { color: any; opacity: any; transparent: boolean; depthWrite: boolean; map: any }
  constructor(e, t, i, s, r, o, a, c = new THREE.Color(16724312), h = new THREE.Color(16724312), d = 0.8, u = 0.8, p = 300, m = 0.4, g = RenderLayers.ALL) {
    this.scene = e
    this.input = t
    this.settingsData = i
    this.sweepViewData = s
    this.puckImagery = r
    this.onFloor = o
    this.checkRenderModes = a
    this.idleColor = c
    this.selectionColor = h
    this.idleOpacity = d
    this.editingOpacity = u
    this.selectionDuration = p
    this.puckSize = m
    this.layer = g
    this.sweepToMesh = {}
    this.colliderToSweep = {}
    this.editingEnabled = !1
    this.dummyMat = new THREE.MeshBasicMaterial()
    this.puckOffset = new THREE.Vector3(0, 0.05, 0)
    this.bindings = []
    this.wasDrawing = !1
    this.renderPuckHighlight = (e, t) => {
      if (!this.sweepViewData.isSweepAligned(e)) return
      const i = this.sweepToMesh[e].material.color
      t ? i.copy(this.selectionColor) : i.copy(this.idleColor)
    }
    this.updateLookAt = e => {
      this.sweepViewData.iterate(t => {
        const i = this.sweepToMesh[t.id]
        i && i.visible && i.lookAt(e)
      })
    }
  }
  init() {
    this.puckGeometry = new THREE.PlaneBufferGeometry(this.puckSize, this.puckSize)
    if (this.onFloor) {
      const e = new THREE.Matrix4()
      e.makeRotationFromEuler(new THREE.Euler(-Math.PI / 2, 0, 0, "XYZ"))
      this.puckGeometry.applyMatrix4(e)
    }
    this.sphereColliderGeometry = new THREE.SphereBufferGeometry(0.5 * this.puckSize, 10, 10)
    this.matParamsEnabled = {
      color: this.idleColor.getHex(),
      opacity: this.idleOpacity,
      transparent: !0,
      depthWrite: !1,
      map: this.puckImagery.enabled
    }
    this.sweepViewData.iterate(this.createPuck.bind(this))
  }
  get displayDisabled() {
    return void 0 !== this.puckImagery.disabled
  }
  onSweepStateAnimationChanged(e: Sweep, t: THREE.MeshBasicMaterial, i) {
    const n = i.value
    const s = e.enabled ? this.puckImagery.enabled : this.puckImagery.disabled
    const r = e.enabled ? this.puckImagery.enabledHover : this.puckImagery.disabledHover
    const o = n > 0 && this.editingEnabled
    const a = e.id === this.sweepViewData.selectedSweep
    const l = this.editingEnabled && a ? this.selectionColor : this.idleColor
    const c = this.editingEnabled ? this.editingOpacity : this.idleOpacity
    t.setValues({ map: o ? r : s })
    t.color.copy(l)
    t.opacity = c * (1 - n) + 1 * n
  }
  createMaterial(e: Sweep) {
    const t = new THREE.MeshBasicMaterial(this.matParamsEnabled)
    this.puckImagery.disabled && !e.enabled && t.setValues({ map: this.puckImagery.disabled })
    this.bindings.push(
      e.onPropertyChanged("enabled", () => {
        t.setValues({
          map: e.enabled ? this.puckImagery.enabled : this.puckImagery.disabled
        })
      })
    )
    return t
  }
  createMesh(e: Sweep, t: THREE.MeshBasicMaterial) {
    const i = new PuckMesh(this.puckGeometry, t)
    this.onFloor ? i.position.copy(e.floorPosition).add(this.puckOffset) : (i.position.copy(e.position), i.rotation.setFromQuaternion(e.rotation))
    i.name = e.id
    i.updateMatrixWorld(!1)
    i.renderOrder = renderOrderEnum.panoMarker
    const n = this.sweepViewData.getState(e.id)
    i.visible = n.visible
    this.bindings.push(
      n.onPropertyChanged("visible", e => {
        i.visible = e
      })
    )
    this.bindings.push(n.onPropertyChanged("animation", i => this.onSweepStateAnimationChanged(e, t, i)))
    i.layers.mask = this.layer.mask
    return i
  }
  createCollider(e: Sweep, t: PuckMesh) {
    let i: PuckCollider
    i = this.onFloor ? new PuckCollider(this.puckGeometry, this.dummyMat) : new PuckCollider(this.sphereColliderGeometry, this.dummyMat)
    i.userData.sid = e.id
    i.position.copy(t.position)
    i.rotation.copy(t.rotation)
    i.name = e.id
    i.updateMatrixWorld(!1)
    return i
  }
  createPuck(e: Sweep) {
    if (e.alignmentType !== sweepAlignmentEnum.ALIGNED) return
    const t = this.createMaterial(e)
    const i = this.createMesh(e, t)
    const n = this.createCollider(e, i)
    i.collider = n
    this.sweepToMesh[e.id] = i
    this.colliderToSweep[n.id] = e.id
  }
  toggleEditingEnabled(e) {
    this.editingEnabled = e
    for (const t in this.sweepToMesh) {
      this.sweepToMesh[t].material.opacity = e ? this.editingOpacity : this.idleOpacity
    }
  }
  getSweepId(e: string) {
    return this.colliderToSweep[e]
  }
  updatePuckImagery(e: typeof sweepStatus) {
    let t
    this.displayDisabled || void 0 === e.disabled ? this.displayDisabled && void 0 === e.disabled && (t = !1) : (t = !0)
    this.puckImagery = e
    this.sweepViewData.iterate(e => {
      if (e.alignmentType !== sweepAlignmentEnum.ALIGNED) return
      const i = this.sweepToMesh[e.id]
      const n = this.sweepViewData.getState(e.id)
      i.visible = n.visible
      let s = this.puckImagery.enabled
      if ((!e.enabled && this.puckImagery.disabled && (s = this.puckImagery.disabled), this.sweepToMesh[e.id].material.setValues({ map: s }), void 0 !== t)) {
        if (e.enabled) return
        t ? this.addSweepToScene(e.id) : this.removeSweepFromScene(e.id)
      }
    })
  }
  updateCheckRenderModes(e) {
    this.checkRenderModes = e
  }
  removeSweepFromScene(e: string) {
    const t = this.sweepToMesh[e]
    this.scene.remove(t)
    this.input.unregisterMesh(t.collider)
  }
  addSweepToScene(e: string) {
    if (!this.sweepViewData.getSweep(e).enabled && !this.displayDisabled) return
    const t = this.sweepToMesh[e]
    this.scene.add(t)
    this.input.registerMesh(t.collider, !0)
  }
  render() {
    const e = this.checkRenderModes()
    const t = this.settingsData.tryGetProperty(keyConst.rule.features.sweep, !0)

    if (e && t) {
      if (!this.wasDrawing) {
        for (const e in this.sweepToMesh) this.addSweepToScene(e)
        this.wasDrawing = !0
      }
    } else if (this.wasDrawing) {
      for (const e in this.sweepToMesh) this.removeSweepFromScene(e)
      this.wasDrawing = !1
    }
  }
  dispose() {
    for (const e in this.sweepToMesh) {
      const t = this.sweepToMesh[e]
      t.material.dispose()
      t.geometry.dispose()
    }
  }
  activate(e: EngineContext) {
    this.wasDrawing = !1
    const t = new PuckComparator(this.sweepViewData)
    this.bindings.push(
      this.input.registerMeshHandler(HoverEvent, t, (t, i) => {
        e.commandBinder.issueCommand(new SweepHoverCommand(this.getSweepId(i.id), !0, this.selectionDuration)),
          e.broadcast(new PuckHoverMessage(this.getSweepId(i.id), !0))
      })
    )
    this.bindings.push(
      this.input.registerMeshHandler(UnhoverEvent, t, (t, i) => {
        e.commandBinder.issueCommand(new SweepHoverCommand(this.getSweepId(i.id), !1, this.selectionDuration)),
          e.broadcast(new PuckHoverMessage(this.getSweepId(i.id), !1))
      })
    )
    this.bindings.push(
      this.input.registerMeshHandler(ClickEvent, t, (t, i) => {
        const n = this.sweepViewData.getSweep(this.colliderToSweep[i.id]).index
        const s = this.sweepViewData.getAlignedSweeps(!0).length
        e.broadcast(new PuckClickedMessage(this.colliderToSweep[i.id], n, s))
      })
    )
  }
  deactivate(e) {
    for (const e in this.sweepToMesh) this.removeSweepFromScene(e)
    for (const e of this.bindings) e.cancel()
    this.bindings.length = 0
  }
}
class PuckComparator {
  sweepViewData: SweepViewData
  constructor(e: SweepViewData) {
    this.sweepViewData = e
  }
  compare(e: THREE.Object3D) {
    if (e instanceof PuckCollider) {
      return this.sweepViewData.getSweepVisibility(this.sweepViewData.getSweep(e.userData.sid))
    }
    return !1
  }
}
